package binocle

import (
	"fmt"
	"io"
	"strings"
	"sync"
	"time"

	"gddgame.cc/galaxy/asteroid"
	"gddgame.cc/galaxy/asteroid/cluster"
	"gddgame.cc/galaxy/asteroid/socket/super"
	"gddgame.cc/galaxy/asteroid/strategy/hash"
	"gddgame.cc/galaxy/binocle/logic"
	"gddgame.cc/galaxy/binocle/module/no"
	"gddgame.cc/galaxy/binocle/module/notice"
	"gddgame.cc/galaxy/binocle/module/proxy"
	"gddgame.cc/galaxy/binocle/module/reload"
	"gddgame.cc/galaxy/binocle/module/report"
	"gddgame.cc/galaxy/binocle/module/schedule"
	"gddgame.cc/galaxy/binocle/module/socket"
	"gddgame.cc/galaxy/utils/cache"
	"gddgame.cc/galaxy/utils/def"
	"github.com/antlabs/timer"
)

type App interface {
	AgentClient

	cluster.Container
	socket.AppSocket
}

type Service interface {
	AgentClient

	logic.Cloud
	socket.ServiceSocket
	FillName(name string) string
}

type AgentClient interface {
	Version() string
	logic.System
	logic.Observer

	report.AgentReporter
	schedule.AgentScheduler
	notice.AgentNoticer
	no.Noer
	proxy.AgentProxy
	Context() def.Context
	Cluster() cluster.Engine
	Name() string
	FullName() string
	Namespace() string
}

type Agent interface {
	Cluster() cluster.Engine
	Application(config Config) (App, error)
	Service(config Config) (Service, error)
	Close() error
	Start() error
}
type agentClient struct {
	agent *agent
	name  string

	adapt    asteroid.Adapt
	layer    *asteroid.Layer
	region   *asteroid.Region
	groupSub asteroid.Sub
	sub      asteroid.Sub

	logic.System
	*logic.ObserverInstance

	report.AgentReporter
	schedule.AgentScheduler
	notice.AgentNoticer
	no.Noer
	proxy.AgentProxy
	socket.AgentSocket
}

type appAgent struct {
	agentClient

	cluster.Container
}
type serviceAgent struct {
	agentClient

	logic.Cloud
}

type agent struct {
	def.Logger
	timer.Timer
	version string
	name    string
	group   string
	server  string

	cluster      cluster.Engine
	namespace    string
	adaptHandler AdaptHandler

	connect *logic.Connect

	agents      map[string]io.Closer
	contextPool sync.Pool
}

func NewAgent(logger def.Logger, namespace string, group string, adaptFn AdaptHandler) *agent {
	agent := &agent{
		Logger:       logger,
		namespace:    namespace,
		group:        group,
		adaptHandler: adaptFn,

		agents: map[string]io.Closer{},
	}
	agent.Timer = timer.NewTimer()
	agent.cluster = cluster.NewEngine()
	agent.cluster.SetLogger(agent.LoggerWrap("Binocle", "cluster"))
	agent.connect = logic.NewConnect(agent.LoggerWrap("Binocle", "connect"), agent)

	agent.contextPool = sync.Pool{New: func() interface{} {
		context := &Context{c: agent}
		context.Logger = agent.Logger.LoggerWrap("agent", "context")
		context.Store = &cache.Store{
			Data: make(map[string]interface{}),
		}
		return context
	}}
	return agent
}

func (agent *agent) Application(config Config) (App, error) {
	if config.Meta == nil {
		config.Meta = make(map[string]string)
	}
	config.Meta["type"] = "application"
	layer, adapt, err := agent.initNode(config)
	if err != nil {
		return nil, err
	}
	client, err := agent.initClient(config, layer, adapt)
	if err != nil {
		return nil, err
	}
	app := &appAgent{
		agentClient: *client,
	}
	app.Container = agent.cluster.Register(config.Name+".app", client.region)

	agent.agents[config.Name] = app

	// 发送启动信息： 如果没有core将发送失败
	data := make(map[string]interface{})
	for key := range config.Meta {
		data[key] = config.Meta[key]
	}
	app.OnObserved(func() {
		app.SendNotice("server.startup", data)
	})
	return app, nil
}
func (agent *agent) Service(config Config) (Service, error) {
	return agent.service(config)
}
func (agent *agent) Cluster() cluster.Engine {
	return agent.cluster
}
func (agent *agent) FullName(target string) string {
	if strings.Contains(target, agent.group) {
		return target
	}
	if agent.server != "" && agent.server != target {
		target += "." + agent.server
	} else if agent.server == "" && agent.name != target {
		target += "." + agent.name
	}
	if agent.group != DefaultGroup {
		target += "." + agent.group
	}

	return target
}

func (agent *agent) service(config Config) (*serviceAgent, error) {
	if config.Meta == nil {
		config.Meta = make(map[string]string)
	}
	if v, ok := config.Meta[ServerMeta]; !ok {
		config.Meta[ServerMeta] = "all"
	} else {
		agent.server = v
	}
	config.Meta["type"] = "service"
	agent.version = config.Version
	layer, adapt, err := agent.initNode(config)
	if err != nil {
		return nil, err
	}
	client, err := agent.initClient(config, layer, adapt)
	if err != nil {
		return nil, err
	}
	service := &serviceAgent{
		agentClient: *client,
	}

	// service启动云模式，提供多租户隔离
	service.Cloud = logic.NewCloud(config.Name, client.layer, agent.cluster, agent.LoggerWrap("Binocle", "cloud"))

	agent.agents[config.Name] = service

	// 发送启动信息： 如果没有core将发送失败
	data := make(map[string]interface{})
	for key := range config.Meta {
		data[key] = config.Meta[key]
	}
	service.OnObserved(func() {
		service.SendNotice("server.startup", data)
	})
	return service, nil
}

func (agent *agent) initClient(config Config, layer *asteroid.Layer, adapt asteroid.Adapt) (*agentClient, error) {
	config.Meta["start_time"] = time.Now().Format("2006-01-02 15:04:05")
	config.Meta["group"] = agent.group
	//config.Meta["name"] = config.Name
	//config.Meta["node"] = config.Node
	agent.name = config.Name
	name := config.Name
	name = agent.FullName(name)

	region, _ := layer.Region(name)
	region.SetStrategy(hash.NewStrategy())

	client := &agentClient{
		agent:  agent,
		name:   config.Name,
		adapt:  adapt,
		layer:  layer,
		region: region,
	}
	// 提供服务接口
	client.sub = client.layer.Channel.Subscribe(config.Name)
	if name != client.name {
		client.groupSub = client.layer.Channel.Subscribe(name)
	} else {
		client.groupSub = client.sub
	}
	// 共用一个通道
	// 注册到集群
	client.ObserverInstance = logic.NewObserver(config.Name, name, DefaultCore, &layer.Channel, config.Meta)

	p := proxy.NewAgent(client.ObserverInstance, client.sub)
	// 绑定代理接口
	layer.BindWorker(p)
	client.AgentProxy = p

	// 启动一个系统内部通信接口
	client.System = logic.NewSystem(region, agent.cluster)

	// 绑定自动更新模块
	//if config.Version != "" {
	//	upgrade.Module(config.Version, config.VersionUpgrade, client.System)
	//}
	// 绑定重启模块
	reload.NewAgent(client.ObserverInstance, client.sub)
	// 绑定report模块
	client.AgentReporter = report.NewAgent(client.ObserverInstance, client.sub)
	//fmt.Println("manager")
	// 绑定统一配置,全局计划任务
	client.AgentScheduler = schedule.NewAgent(client.ObserverInstance, client.sub)
	client.AgentNoticer = notice.NewAgent(client.ObserverInstance, client.sub)
	client.Noer = no.NewAgent(client.ObserverInstance, client.groupSub)

	// 设置socket模块
	client.AgentSocket = socket.NewSocket(name, agent.LoggerWrap("Binocle", "socket"), agent, client.groupSub, &client.layer.Channel)

	//fmt.Println("startUp")
	client.System.StartUp()

	//fmt.Println("finish")
	return client, nil
}

func (agent *agent) initNode(config Config) (*asteroid.Layer, asteroid.Adapt, error) {
	layer, err := agent.connect.Connect(config.Node)
	if err != nil {
		return nil, nil, err
	}
	if !config.Local {
		socket := super.NewSocket(agent)
		if err := layer.SetSocket(socket); err != nil {
			return nil, nil, err
		}
	}
	adapt := agent.adaptHandler(agent.namespace + ".")
	if adapt == nil {
		return layer, nil, nil
	}
	if err := layer.StartChannel(adapt); err != nil {
		return nil, nil, err
	}
	if err := layer.SetRegionAdapt(adapt); err != nil {
		return nil, nil, err
	}
	return layer, adapt, nil
}

func (agent *agent) Version() string {
	return agent.version
}
func (agent *agent) Start() error {
	go func() {
		agent.Timer.Run()
	}()
	return nil
}
func (agent *agent) Close() error {
	//if err := agent.connect.Exit(); err != nil {
	//	return err
	//}
	//if err := agent.cluster.Exit(); err != nil {
	//	return err
	//}
	agent.Timer.Stop()
	// 等待集群停止，执行关闭
	for _, c := range agent.agents {
		if err := c.Close(); err != nil {
			return err
		}
	}
	if err := agent.cluster.Close(); err != nil {
		return err
	}
	return nil
}
func (client *agentClient) Context() def.Context {
	return client.agent.contextPool.Get().(*Context)
}
func (client *agentClient) Version() string {
	return client.agent.version
}
func (client *agentClient) Cluster() cluster.Engine {
	return client.agent.cluster
}
func (client *agentClient) Name() string {
	return client.name
}
func (client *agentClient) FullName() string {
	return client.agent.FullName(client.name)
}
func (client *agentClient) FillName(name string) string {
	return client.agent.FullName(name)
}
func (client *agentClient) Namespace() string {
	return client.agent.namespace
}
func (client *agentClient) Close() error {
	if err := client.layer.Close(); err != nil {
		fmt.Println("agent layer", err)
		return err
	}
	if client.adapt != nil {
		if err := client.adapt.Close(); err != nil {
			fmt.Println("agent adapt", err)
			return err
		}
	}
	return nil
}
